Version with answers

This output of the TD includes answers to questions.

PreReq

  1. I know how to code in Java.

  2. I know I need to think before I start coding.

  3. I know basic OO concepts (inheritance, polyporphism, …​).

ObjTD

Understand the importance of Design.

Duration

1 TD and 2 TPs (spread on 2 weeks)

1. The "SuperCanard" application

This TD exercice is inspired from the excellent book "Head First: Design Pattern". Bert Bates, Eric Freeman, Elisabeth Freeman, Kathy Sierra. Editions O’Reilly. 2005.

1.1. Existing application

You are asked to work on an existing app SuperCanard (duck, called canard in French, simulation game) which model (sorry for the French) is provided in the following class diagram:

superCanard
Figure 1. Existing app model (plantUML source here)
Some other classes inherit from Canard.

Here is a code example:

First version of Canard.java
abstract public class Canard {

	public void cancaner() {
		System.out.println("Je cancane comme un Canard!");
	}

	public void nager() {
		System.out.println("Je nage comme un Canard!");
	}

	abstract public void afficher();
}
First version of Colvert.java
public class Colvert extends Canard {

	public void afficher() {
		System.out.println("Je suis un Colvert");
	}

}

1.2. Modification/Improvement

Your boss requires that you upgrade the application in order to be a little more realistic.

You decide to add a voler() method to all your ducks:

superCanard2 note
Figure 2. New feature
Second version of Canard.java
abstract public class Canard {

	public void cancaner() {
		System.out.println("Je cancane comme un Canard!");
	}

	public void nager() {
		System.out.println("Je nage comme un Canard!");
	}

	abstract public void afficher();

	public void voler() {
		System.out.println("Je vole comme un Canard!");
	};
}

1.3. #WTF!

You receive an emergency call from your boss: in the application some plastic ducks start to fly!!! In addition, sick ducks, that shouldn’t fly, do so!

You forgot that some kind of ducks do not fly!
QUESTION

Complete this sentence: Inheritance is great to do …​…​…​…​. but is more problematic in terms of …​…​…​…​.

Solution

Inheritance is great to do reuse but is more problematic in terms of maintenance (or evolution).

1.4. Solution 1: redefine the methods

The first solution that comes to your mind is simple: redefine the voler() method for the ducks who don’t fly.

QUESTION

Complete the following java code to implement this solution:

public class CanardEnPlastique extends Canard {

	@Override
	public void afficher() {
		System.out.println("Je suis un CanardEnPlastique!");
	}






}
Solution

public class CanardEnPlastique extends Canard {

	@Override
	public void cancaner() {
		System.out.println("Je couine puisque je suis en plastique!");
	}

	@Override
	public void nager() {
		System.out.println("Je flotte comme du plastique!");
	}

	@Override
	public void voler() {
		System.out.println("Je ne vole pas, je suis en plastique!!!");
	};

	@Override
	public void afficher() {
		System.out.println("Je suis un CanardEnPlastique!");
	}

}
QUESTION

In the following list, what are the problems that inheritance can raise to define the behavior of a Canard? (Possibly mutliple good answers) :

  • Code is duplicated (rewritten) between sub-classes.

  • Behavior changes at run-time are complicated.

  • We cannot have dancing ducks.

  • It is hard to know all the ducks' behaviors

  • Ducks cannot fly and sing at same time.

  • Modifications can modify unexpectedly other ducks' behavior.

Solution
  • Code is duplicated (rewritten) between sub-classes.

  • Behavior changes at run-time are complicated.

  • We cannot have dancing ducks.

  • It is hard to know all the ducks' behaviors

  • Ducks cannot fly and sing at same time.

  • Modifications can modify unexpectedly other ducks' behavior.

1.5. Solution 2: use of interfaces

You know try the use of interfaces to improve the code.

QUESTION
  1. On the following diagram, place the inheritance relations (java extends) and the implementation relations (java implements):

    superCanardInterfaces
  2. What do you think of the final result ?

Solution
superCanardInterfacesSolution

1.6. Solution 3: isolate what change

You realize you’re facing the kind of problem you had in the MPA module: CHANGES!

Let us then apply our first good principle :

Good design principle

Identify aspects of your code that vary and separate them from the one that don’t.

QUESTION

What are the two main things that vary in your code?

Solution

voler() and cancaner() ⇒ behavior.

1.6.1. Implementing behaviors

Let’s try to implement behavior differently, so that they are separated from the rest of the code. For that we will use another good principle:

Good design principle

Program an interface, not an implementation.

QUESTION

Propose a design (class diagram only) with the following classes and/or interfaces (you’ll have to decide): ComportementVol, VolerAvecDesAiles, NePasVoler.

Solution

We have included in the solution ComportementCancan.

comportement

Question: what is the difference between an abstract class and an interface to express behaviors?

1.6.2. Adding the new behaviors to the code

We have now to somehow link the behaviors to their corresponding ducks' class.

QUESTION
  1. Add to the Canard class two attributes to reference their behaviors.

  2. Remove the useless methods.

  3. Replace them (provide the corresponding code) by the methods: effectuerVol() and effectuerCancan() (using the new attributes).

  4. Modify the constructors of Colvert (for example) how the attributes are initialized.

  5. Add to Colvert the setMalade() and setGueri() methods that allow at run-time to modify the flying behavior.

Solution
New version of Canard.java
/**
 * @author bruel
 * @navassoc - - comportementVol ComportementVol
 * @navassoc - - comportementCancan ComportementCancan
 */
abstract public class Canard {

	/**
	 * @overrideAssoc
	 */
	protected ComportementVol comportementVol;
	/**
	 * @overrideAssoc
	 */
	protected ComportementCancan comportementCancan;

	public final void effectuerCancan() {
		comportementCancan.cancaner();
	}

	public void nager() {
		System.out.println("Je nage comme un Canard!");
	}

	abstract public void afficher();

	//
	public final void effectuerVol() {
		comportementVol.voler();
	}

}
New version of Colvert.java
public class Colvert extends Canard {

	public Colvert() {
		comportementCancan = new Cancan();
		comportementVol = new VolerAvecDesAiles();
	}
	@Override
	public void afficher() {
		System.out.println("Je suis un Colvert");
	}

}
public void setMalade () { this.comportementVol = new NePasVoler(); }
public void setGueri () { this.comportementVol = new VolerAvecDesAiles(); }

1.6.3. Summary and discussions

Let us now have a look at the overall design we have obtained.

QUESTION
  1. Draw the class diagram of the new application.

  2. What would you modify to add for example a new flying mode (e.g., propulsion à réaction) ?

  3. Could you thhink of a class that could use the Cancan behavior without being a duck?

Solution
  1. Class diagram:

    superCanardFinal
  2. Propulsion à réaction:

    Simply create a new class VolAReaction that implements the ComportementVol interface.

  3. Class that is not a Canard:

    Par exemple un appeau (sorry I don’t have the English name for that, but for our non French speaking fellows, it is a kind of wissle that imitates the duck sound, used by hunters)!

1.7. Your first Design Pattern

1.7.1. The Strategy pattern

In fact you have just implemented your first Design Pattern : the Strategy pattern (Stratégie), sorry for the French:

Design pattern: Stratégie (Strategy)

Stratégie définit une famille d’algorithmes, encapsule chacun d’eux et les rend interchangeables. Il permet à l’algorithme de varier indépendamment des clients qui l’utilisent.

strategy
Figure 3. Modèle UML du patron Strategy
google strategy
Figure 4. Some examples of descriptions of Strategy

1.7.2. Let’s try it on another application

You are asked to rework on an application where only the following model was produced (sorry again for the damn French):

aventure sujet
  1. Reorganize the classes

  2. Identify abstract classes, interfaces and regular classes.

  3. Trace the links between classes ("is a", implementation, "has a")

  4. Place the following setArme() method on the correct class:

    setArme(ComportementArme a) {
      this.arme = a;
    }
Solution
aventure

Still hungry?

We have used without mentioning it a 3rd good principle:

Good design principle

Prefer composition than inheritance.

QUESTION

What difference is there between our final design and this kind of implementation?:

abstract public class Canard implements ComportementVol {...}
crossword1
Figure 5. CrossWords (sorry, in French)
This crossword is taken from the book mentioned earlier, and hence includes definitions of words that you can’t guess:
  • 7 ⇒ Baleares

  • 11 ⇒ Observateur

  • 12 ⇒ DK

Solution
crossword1 sol
QUESTION

How would you test the presence of a Strategy pattern in an implementation ?

Solution
require "minitest/autorun"
MODEL_NAME = "model.uml"

module MiniTest
  class Unit
    class TestCase
      #Define new assertion
      def assert_contains(string_to_test, substring_to_verify)
        assert_match( string_to_test, substring_to_verify)
      end
      def assert_not_contains(string_to_test, substring_to_verify)
        assert_not_match( string_to_test, substring_to_verify)
      end
    end
  end
end
MiniTest::Unit.after_tests { p @_assertions }

class TestGeneratedModel < MiniTest::Unit::TestCase
  #------------ General tests about plantUML
  def test_generated_model_exists
    print assert_equal(true, File.exists?(MODEL_NAME))
  end

  def test_generated_model_is_plantuml
    assert_equal(true, File.readlines(MODEL_NAME).grep(/@startuml/).any?)
    assert_equal(true, File.readlines(MODEL_NAME).grep(/@enduml/).any?)
  end

  def test_generated_model_exists
    assert_equal(true, File.exists?(MODEL_NAME))
  end

  #------------ Specific tests about expected content
  def test_class_Canard_is_abstract
    assert_equal(true, File.readlines(MODEL_NAME).grep(/abstract Canard/).any?)
  end

  def test_class_Canard_has_ComportementCancan_behavior
    assert_contains(/Canard\s+--> ".*" ComportementCancan/, File.readlines(MODEL_NAME).join)
  end

  def test_class_Canard_has_ComportementVol_behavior
    assert_contains(/Canard\s+--> ".*" ComportementVol/, File.readlines(MODEL_NAME).join)
  end

  def test_ComportementCancan_is_an_Interface
    assert_equal(true, File.readlines(MODEL_NAME).grep(/interface\s+ComportementCancan/).any?)
  end

  def test_ComportementVol_is_an_Interface
    assert_equal(true, File.readlines(MODEL_NAME).grep(/interface\s+ComportementVol/).any?)
  end

  def test_ComportementCancan_Interface_has_concrete_implementation
    assert_equal(true, File.readlines(MODEL_NAME).grep(/ComportementCancan\s+<\|\.\./).any?)
  end

  def test_ComportementVol_Interface_has_concrete_implementation
    assert_equal(true, File.readlines(MODEL_NAME).grep(/ComportementVol\s+<\|\.\./).any?)
  end

end
Do not hesitate to have a look at this other role playing example, available here (p.116).